package edu.hawaii.wattdepotapps.monitor;

import java.io.Serializable;
import org.wattdepot.client.BadXmlException;
import org.wattdepot.client.MiscClientException;
import org.wattdepot.client.NotAuthorizedException;
import org.wattdepot.client.ResourceNotFoundException;
import org.wattdepot.resource.sensordata.jaxb.SensorData;
import org.wattdepot.client.WattDepotClient;

/**
 * 
 * Stores a Source's latest SensorData and returns various properties of that SensorData object.
 * 
 * @author Bao Huy Ung, Edward Meyer, Kendyll Doi
 * @version 2.0
 * 
 */
public class WattDepotLatestSensorDataCommand implements Serializable {

  /** Serialize this object to work with Wicket components. */
  private static final long serialVersionUID = 4226738932722470940L;

  /** Holds the latest sensor data for a given source. */
  private SensorData sensorData = null;
  /** Holds the requested data type from the Monitor. */
  private String dataType = "";
  /** Holds the requested interval frequency. */
  private String interval = "";
  /** Holds the name of the source being monitored. */
  private String sourceName = "";

  /**
   * Constructor for WattDepotLatestSensorDataCommand object, stores a SensorData object given a
   * source.
   * 
   * @param args Is a 2 element array that contains the source and data type requested from the
   * Monitor.
   * @param client Is the connection to a WattDepot server.
   */
  public WattDepotLatestSensorDataCommand(String args[], WattDepotClient client) {
    // Since the Monitor communicates with WattDepot, need to catch all errors
    // that might occur when accessing SensorData from the client.
    try {
      sensorData = client.getLatestSensorData(args[0]);
      sourceName = args[0];
      dataType = args[1];
      interval = args[2];
    }
    // Source does not allow anonymous/public access.
    catch (NotAuthorizedException e1) {
      System.err.format("Source %s does not allow public/anonymous access.", args[0]);
    }
    // Source does not have any sensor data.
    catch (ResourceNotFoundException e1) {
      System.err.format("Source %s does not have any sensor data.", args[0]);
    }
    // Received bad XML from server.
    catch (BadXmlException e1) {
      System.err.println("Received bad XML from server.");
    }
    // Any other miscellaneous errors that occur will be caught by MiscClientException.
    catch (MiscClientException e1) {
      System.err.println("Had problems retrieving source from server." + e1);
    }
  }

  /**
   * Returns a given source's latest SensorData as a String formatted to 2 decimal places with
   * proper units. Type to return is already determined in the construction of each
   * WattDepotLatestSensorDataCommand object.
   * 
   * @return A string representing the latest sensor data.
   */
  public String getSensorData() {

    // Try block if anything goes wrong during data retrieval.
    try {
      // Depending on the type selected, call the appropriate latest method from WattDepot.
      if ("Power Generated".equals(dataType)) {
        return convertUnits(sensorData.getProperty(SensorData.POWER_GENERATED), false);
      }
      else if ("Power Consumed".equals(dataType)) {
        return convertUnits(sensorData.getProperty(SensorData.POWER_CONSUMED), false);
      }
      else if ("Energy Consumed to Date".equals(dataType)) {
        return convertUnits(sensorData.getProperty(SensorData.ENERGY_CONSUMED_TO_DATE), true);
      }
      else if ("Energy Generated to Date".equals(dataType)) {
        return convertUnits(sensorData.getProperty(SensorData.ENERGY_GENERATED_TO_DATE), true);
      }
      else {
        return "Connection Failure";
      }
    }
    // If a null pointer occurred, then the source has no data for the specified type.
    catch (NullPointerException e) {
      return "No Data";
    }
  }

  /**
   * Helper method that converts Watts into a higher gradient, formatted with 2 decimal places.
   * Example: If higher than 10^9, converts to GigaWatts, higher than 10^6 converts to MegaWatts,
   * etc.
   * 
   * @param stringValue Is the watt value to be converted.
   * @param hourFlag If a choice is a "To Date" type, add hour to the unit type.
   * 
   * @return A string value that represents the watts in a different unit gradient.
   */
  private String convertUnits(String stringValue, Boolean hourFlag) {
    // Double used to parse the data in the sensor object.
    Double doubleValue = null;
    // Output string to that will output to the Monitor.
    String output = "";
    // Format string that formats the decimals to 2 decimal places.
    String formatRegex = "%.2f";

    // Parse the string value given from the Sensor Data.
    try {
      doubleValue = Double.parseDouble(stringValue);
    }
    catch (NumberFormatException e) {
      return null;
    }

    /*
     * Determine how big the unit gradient will be, format the number to 2 decimal places, and
     * attach the appropriate unit abbreviation to the end of the string.
     */
    if (doubleValue >= 1000000000) {
      output = String.format(formatRegex, doubleValue / 1000000000) + " GW";
    }
    else if (doubleValue >= 1000000) {
      output = String.format(formatRegex, doubleValue / 1000000) + " MW";
    }
    else if (doubleValue >= 1000) {
      output = String.format(formatRegex, doubleValue / 1000) + " kW";
    }
    else {
      output = String.format(formatRegex, doubleValue) + " Watt";
    }

    // If the output requires hours at the end, append the appropriate abbreviation.
    if (hourFlag) {
      // If the value is less than 1000, have the units as "Watt Hours" instead of " Watth".
      if (doubleValue < 1000) {
        output = output + " Hours";
      }
      else {
        output = output + "h";
      }
    }

    return output;
  }

  /**
   * Helper method that returns the timestamp of when the source was last updated.
   * 
   * @return A string representation of the timestamp last updated.
   */
  public String getLatestSensorDataTimestamp() {
    // Return the timestamp of when the source was last updated.
    try {
      return sensorData.getTimestamp().toString();
    }
    catch (Exception e) {
      return null;
    }
  }

  /**
   * Helper method that returns the name of the source.
   * 
   * @return The name of the source.
   */
  public String getSourceName() {
    // This is used rather than the SensorData's getSource method because SensorData appends the web
    // address to the name.
    return sourceName;
  }

  /**
   * Helper method that returns the data type being retrieved.
   * 
   * @return The data type being monitored.
   */
  public String getDataType() {
    return dataType;
  }

  /**
   * Returns the selected interval the user selected.
   * 
   * @return The selected interval as a string.
   */
  public String getInterval() {
    return interval;
  }
}
